winsafe\kernel\utilities/
file.rs

1use crate::co;
2use crate::decl::*;
3use crate::guard::*;
4
5/// Access types for [`File::open`](crate::File::open) and
6/// [`FileMapped::open`](crate::FileMapped::open).
7#[derive(Clone, Copy, PartialEq, Eq, Hash)]
8pub enum FileAccess {
9	/// Opens the file as read-only. Fails if the file doesn't exist.
10	ExistingReadOnly,
11	/// Opens the file as read/write. Fails if the file doesn't exist.
12	ExistingRW,
13	/// Opens the file as read/write. If the file doesn't exist, it will be
14	/// created.
15	OpenOrCreateRW,
16	/// Creates a new file as read/write. Fails if the file already exists.
17	CreateRW,
18}
19
20impl std::fmt::Display for FileAccess {
21	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22		write!(
23			f,
24			"{}",
25			match self {
26				FileAccess::ExistingReadOnly => "Existing file, read-only",
27				FileAccess::ExistingRW => "Existing file, read and write",
28				FileAccess::OpenOrCreateRW =>
29					"Open existing file or create new file, read and write",
30				FileAccess::CreateRW => "Create new file, read and write",
31			}
32		)
33	}
34}
35
36/// Manages an [`HFILE`](crate::HFILE) handle, which provides file read/write
37/// and other operations. It is closed automatically when the object goes out of
38/// scope.
39///
40/// This is an alternative to the standard [`std::fs::File`], with a possibly
41/// faster implementation since it's Windows-only.
42///
43/// If you just want to read the file, consider memory-mapping it with
44/// [`FileMapped`](crate::FileMapped), which tends to be faster.
45///
46/// # Examples
47///
48/// Reading the contents as a string:
49///
50/// ```no_run
51/// use winsafe::{self as w, prelude::*};
52///
53/// let f = w::File::open(
54///     "C:\\Temp\\foo.txt",
55///     w::FileAccess::ExistingRW,
56/// )?;
57/// let raw_bytes = f.read_all()?;
58/// let text = w::WString::parse(&raw_bytes)?.to_string();
59/// # w::SysResult::Ok(())
60/// ```
61///
62/// Erasing the file and writing a string:
63///
64/// ```no_run
65/// use winsafe::{self as w, prelude::*};
66///
67/// let f = w::File::open(
68///     "C:\\Temp\\foo.txt",
69///     w::FileAccess::OpenOrCreateRW,
70/// )?;
71/// f.set_size(0)?; // truncate
72/// f.write("My text".as_bytes())?;
73/// # w::SysResult::Ok(())
74/// ```
75pub struct File {
76	hfile: CloseHandleGuard<HFILE>,
77}
78
79impl File {
80	/// Opens a file with the desired access.
81	#[must_use]
82	pub fn open(file_path: &str, access: FileAccess) -> SysResult<Self> {
83		let (acc, share, disp) = match access {
84			FileAccess::ExistingReadOnly => {
85				(co::GENERIC::READ, Some(co::FILE_SHARE::READ), co::DISPOSITION::OPEN_EXISTING)
86			},
87			FileAccess::ExistingRW => {
88				(co::GENERIC::READ | co::GENERIC::WRITE, None, co::DISPOSITION::OPEN_EXISTING)
89			},
90			FileAccess::OpenOrCreateRW => {
91				(co::GENERIC::READ | co::GENERIC::WRITE, None, co::DISPOSITION::OPEN_ALWAYS)
92			},
93			FileAccess::CreateRW => {
94				(co::GENERIC::READ | co::GENERIC::WRITE, None, co::DISPOSITION::CREATE_NEW)
95			},
96		};
97
98		let (hfile, _) = HFILE::CreateFile(
99			file_path,
100			acc,
101			share,
102			None,
103			disp,
104			co::FILE_ATTRIBUTE::NORMAL,
105			None,
106			None,
107			None,
108		)?;
109		Ok(Self { hfile })
110	}
111
112	/// Truncates the file size to zero, then writes the bytes.
113	pub fn erase_and_write(&self, data: &[u8]) -> SysResult<()> {
114		self.set_size(0)?;
115		self.write(data)
116	}
117
118	/// Returns the underlying file handle.
119	#[must_use]
120	pub fn hfile(&self) -> &HFILE {
121		&*self.hfile
122	}
123
124	/// Returns the position of the file pointer by calling
125	/// [`HFILE::SetFilePointerEx`](crate::HFILE::SetFilePointerEx).
126	#[must_use]
127	pub fn pointer_offset(&self) -> SysResult<u64> {
128		self.hfile
129			.SetFilePointerEx(0, co::FILE_STARTING_POINT::CURRENT) // https://stackoverflow.com/a/17707021/6923555
130			.map(|off| off as _)
131	}
132
133	/// Returns the size of the file by calling
134	/// [`HFILE::GetFileSizeEx`](crate::HFILE::GetFileSizeEx), allocates the
135	/// `Vec` buffer, then reads all the file bytes by calling
136	/// [`HFILE::ReadFile`](crate::HFILE::ReadFile).
137	///
138	/// Note that the API limits the reading up to 4 GB.
139	#[must_use]
140	pub fn read_all(&self) -> SysResult<Vec<u8>> {
141		self.set_pointer_offset(0)?;
142		let mut buf = vec![0x00; self.size()? as _];
143		self.read_buffer(&mut buf)?;
144		Ok(buf)
145	}
146
147	/// Calls [`HFILE::ReadFile`](crate::HFILE::ReadFile) to read at most
148	/// `buffer.len()` bytes from the file, starting at the current file pointer
149	/// offset. Returns how many bytes were actually read. The file pointer is
150	/// then incremented by the number of bytes read.
151	///
152	/// Note that the API limits the reading up to 4 GB.
153	pub fn read_buffer(&self, buffer: &mut [u8]) -> SysResult<u32> {
154		self.hfile.ReadFile(buffer)
155	}
156
157	/// Sets the position of the file pointer by calling
158	/// [`HFILE::SetFilePointerEx`](crate::HFILE::SetFilePointerEx).
159	pub fn set_pointer_offset(&self, offset: u64) -> SysResult<()> {
160		self.hfile
161			.SetFilePointerEx(offset as _, co::FILE_STARTING_POINT::BEGIN)
162			.map(|_| ())
163	}
164
165	/// Truncates or expands the file by calling
166	/// [`HFILE::SetFilePointerEx`](crate::HFILE::SetFilePointerEx) and
167	/// [`HFILE::SetEndOfFile`](crate::HFILE::SetEndOfFile), then sets the file
168	/// pointer to the beginning of the file.
169	///
170	/// If the size is increased, the contents in the new area are undefined.
171	///
172	/// Note that sometimes the system will make the file larger than you
173	/// specified, so don't bindly trust this method.
174	pub fn set_size(&self, num_bytes: u64) -> SysResult<()> {
175		self.set_pointer_offset(num_bytes)?;
176		self.hfile.SetEndOfFile()?;
177		self.set_pointer_offset(0)
178	}
179
180	/// Returns the size of the file by calling
181	/// [`HFILE::GetFileSizeEx`](crate::HFILE::GetFileSizeEx).
182	#[must_use]
183	pub fn size(&self) -> SysResult<u64> {
184		self.hfile.GetFileSizeEx()
185	}
186
187	/// Returns, in current time zone, 3 times of the file, respectively:
188	/// 1. creation time;
189	/// 2. last access time;
190	/// 3. last write time.
191	#[must_use]
192	pub fn times(&self) -> SysResult<(SYSTEMTIME, SYSTEMTIME, SYSTEMTIME)> {
193		let (ft_creation, ft_last_access, ft_last_write) = self.hfile.GetFileTime()?;
194
195		let st_creation_utc = FileTimeToSystemTime(&ft_creation)?;
196		let st_last_access_utc = FileTimeToSystemTime(&ft_last_access)?;
197		let st_last_write_utc = FileTimeToSystemTime(&ft_last_write)?;
198
199		let st_creation_local = SystemTimeToTzSpecificLocalTime(None, &st_creation_utc)?;
200		let st_last_access_local = SystemTimeToTzSpecificLocalTime(None, &st_last_access_utc)?;
201		let st_last_write_local = SystemTimeToTzSpecificLocalTime(None, &st_last_write_utc)?;
202
203		Ok((st_creation_local, st_last_access_local, st_last_write_local))
204	}
205
206	/// Writes the bytes at the current file pointer by calling
207	/// [`HFILE::WriteFile`](crate::HFILE::WriteFile).
208	///
209	/// This method will fail if the file was opened with
210	/// [`FileAccess::ExistingReadOnly`](crate::FileAccess::ExistingReadOnly).
211	pub fn write(&self, data: &[u8]) -> SysResult<()> {
212		self.hfile.WriteFile(data).map(|_| ())
213	}
214}